"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.persistence.mysql import PTPDBController, MediaDBController, \
    PlayerDBController, DBValidator

import threading
from msnp.model import MediaModel
from msnp.logger import Logger


ptpDBLock = threading.Lock()
ptpDBLogLock = threading.Lock()
mediaDBLock = threading.Lock()
playerDBLock = threading.Lock()


###################
##### GENERAL #####
###################
def testDBCredentials(host, username, password):
    """Tests whether the database with the given credentials is accessible or not.
    
    Args:
        host: A String representing the host name of the database.
        username: A String representing the user name.
        password: A String representing the password of the user.
    
    Returns:
        True is returned if the database is accessible, False otherwise.
    """
    return DBValidator.testDBCredentials(host, username, password)

def testPlayerDBCredentials(playerDBSettings):
    """Tests whether the player database with the given credentials is accessible or not.
    
    Args:
        playerDBSettings: A DatabaseSettings object containing the connection information.
    
    Returns:
        True is returned if the player database is accessible, False otherwise.
    """
    return DBValidator.testPlayerDBCredentials(playerDBSettings)

###################################
##### PTP-database connections ####
###################################

def initPTPDatabase(host='localhost', username='root', password='root', schema='msnp_ptp'):
    """Initializes the ptp-database.
    
    Sets host, username, password and schema to the corresponding arguments.
    The schema must be set to simulate multiple peers on one system (for testing).
    
    Args:
        host: A String representing the host name of the database. Default localhost.
        username: A String representing the user name. Default root.
        password: A String representing the password of the user. Default root.
        schema: A String representing the schema name. Default msnp_ptp.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        PTPDBController.initDatabaseCredentials(host, username, password, schema)
        PTPDBController.initSchema()
    finally:
        ptpDBLock.release()
    
def cleanDatabase():
    """Cleans the whole database.
    
    Deletes all entries of all tables.
    
    Returns:
        Returns True if the deletion was successful, or False if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        PTPDBController.cleanDatabase()
    finally:
        ptpDBLock.release()
    
    
# log

def log(description):
    """Writes the given String into the log table of the ptp database.
    
    Args:
        description: A String containing the log message.
    """
    global ptpDBLogLock
    
    ptpDBLogLock.acquire()
    try:
        PTPDBController.log(description)
    finally:
        ptpDBLogLock.release()
    
    
# backup

def selectBackup(fromUsername):
    """Selects the specific backup belonging to the given user from the ptp database.
    
    Args:
        fromUsername: A String representing the user name from which the backup should be selected.
    
    Returns:
        A BackupContent object containing the backup of the given user stored in the ptp database is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectBackup(fromUsername)
    finally:
        ptpDBLock.release()

def processBackup(backupContent, overwriteNoneBackupFrom):
    """Inserts, updates or deletes the given backup into/from the ptp database.
    
    Args:
        backupContent: A BackupContent object containing the backup to be inserted into the ptp database.
        overwriteNoneBackupFrom: True if the value None of the column backup_from should be overwritte, otherwise False.
    
    Returns:
        Returns True if the processing was successful, or False if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.processBackup(backupContent, overwriteNoneBackupFrom)
    finally:
        ptpDBLock.release()

def selectBackupFromUsers():
    """Selects all users, that have some backup content stored in the ptp database.
    
    Returns:
        A list of Strings representing all usernames, that store some backup in the ptp database is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectBackupFromUsers()
    finally:
        ptpDBLock.release()
    
# select

def selectUserImResponsibleForUsername(username):
    """Selects a specific user from the database where the current user is responsible of.
    
    Args:
        username: A String representing the name of the searched user.
    
    Returns:
        A User object that contains the user corresponding to the username is returned or None if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectUserImResponsibleForUsername(username)
    finally:
        ptpDBLock.release()

def selectUserGroup(username, groupname, ownername):
    """Selects a specific user-group relation from the ptp database.
    
    Args:
        username: A String representing the user name.
        groupname: A String representing the group name.
        ownername: A String representing the owner name.
    
    Returns:
        A UserGroup object containing the corresponding user-group relation.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectUserGroup(username, groupname, ownername)
    finally:
        ptpDBLock.release()
        
def selectMessagesForUsername(username, delete=False):
    """Selects all messages for a specific username.
    
    Args:
        username: A String representing the user name.
        delete: A boolean value, True if the messages should be deleted, otherwise False. Default False.
    
    Returns:
        A list of MemberStatusChangedMessage objects belonging to the given user name.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectMessagesForUsername(username, delete)
    finally:
        ptpDBLock.release()
        
def selectGroupsForOwnername(ownername):
    """Selects certain groups based on the owner name.
    
    Args:
        ownername: A String representing the name of the owner.
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectGroupsForOwnername(ownername)
    finally:
        ptpDBLock.release()


def selectGroupForGroupnameAndOwnername(groupname, ownername):
    """Selects certain groups based on the group name and owner name.
    
    Args:
        groupname: A String representing the name of the group.
        ownername: A String representing the name of the owner.
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectGroupForGroupnameAndOwnername(groupname, ownername)
    finally:
        ptpDBLock.release()

def selectGroupsForUsername(username):
    """Selects certain groups based on the user name that is a member of the group (has a user-group relation).
    
    Args:
        username: A String representing the user name.
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectGroupsForUsername(username)
    finally:
        ptpDBLock.release()

def selectGroupsForDetails(details):
    """Selects certain groups based on certain details.
    
    Args:
        details: A dictionary of (String, String) containing the details (column name, column value).
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectGroupsForDetails(details)
    finally:
        ptpDBLock.release()
    
def selectAllGroups():
    """Selects all groups.
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectAllGroups()
    finally:
        ptpDBLock.release()


def selectGroupsWithMembersToConfirm(ownername):
    """Selects the groups of a certain owner where at least one member has the status NOT_CONFIRMED.
    
    Args:
        ownername: A String containing the owner name.
    
    Returns:
        A list of Group objects is returned.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.selectGroupsWithMembersToConfirm(ownername)
    finally:
        ptpDBLock.release()
        
def selectChatHistoryByPartnernameAndUsername(partnername, username):
    """Selects all chat messages of a certain user with a certain partner.
    
    Args:
        partnername: A String containing the partner name of the chat message.
        username: A String containing the user name of the chat message.
    
    Returns:
        A list of ChatMessage objects is returned.
    """
    global ptpDBLock
    ptpDBLock.acquire()
    try:
        return PTPDBController.selectChatHistoryByPartnernameAndUsername(partnername, username)
    finally:
        ptpDBLock.release()

# insert

def insertUser(user):
    """Inserts the given User object into the database.
    
    Args:
        user: The User object to be inserted.
    
    Returns:
        True is returned if the insertion was successful, or False if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.insertUser(user)
    finally:
        ptpDBLock.release()
        
def insertMessages(username, messages):
    """Inserts the given messages into the database.
    
    The given list of Message objects is inserted and related to the given username.
    All stored messages, that are not in the list, will be deleted.
    
    Args:
        username: A String representing the user that has to be related with the messages.
        messages: A list of Message objects to be inserted.
    
    Returns:
        True is returned if the insertion was successful, or False if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.insertMessages(username, messages)
    finally:
        ptpDBLock.release()

def establishGroup(group):
    """Inserts the given Group object into the database.
    
    Args:
        group: The Group object to be inserted.
    
    Returns:
        PTPModel.ReturnType.SUCCESSFUL is returned if the insertion was successful,
        PTPModel.ReturnType.GROUP_ALREADY_EXISTS if the group does exist already,
        or PTPModel.ReturnType.ERROR if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.establishGroup(group)
    finally:
        ptpDBLock.release()
        
def updateGroup(group):
    """Updates the corresponding Group object of the database with the values of the given Group object.
    
    Args:
        group: The Group object to be updated.
    
    Returns:
        PTPModel.ReturnType.SUCCESSFUL is returned if the update was successful, or PTPModel.ReturnType.ERROR if an error occurred.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.updateGroup(group)
    finally:
        ptpDBLock.release()
        
def insertMemberGroup(membername, groupname, status, timestamp, ownername, insertGroupIfNotExisting=True):
    """Inserts the given group-member relation into the database.
    
    Args:
        membername: A String containing the member name.
        groupname: A String containing the group name.
        status: A String containing the status of the membership.
        timestamp: A float value containing the exact timestamp of the membership creation.
        ownername: A String containing the owner name.
        insertGroupIfNotExisting: A boolean value, True if the group should be created if not existing, False otherwise. Default True.
    
    Returns:
        PTPModel.ReturnType.SUCCESSFUL is returned if the insertion was successful,
            PTPModel.MemberStatusChangedReturnType.ERROR if an error occurred,
            PTPModel.MemberStatusChangedReturnType.GROUP_NOT_EXISTING if the group to update does not exist in the database,
            or PTPModel.MemberStatusChangedReturnType.ALREADY_MEMBER if the membership does already exist.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.insertMemberGroup(membername, groupname, status, timestamp, ownername, insertGroupIfNotExisting)
    finally:
        ptpDBLock.release()
        
def updateMemberGroup(membername, groupname, ownername, status, timestamp):
    """Updates the corresponding group-member relation of the database with the given values.
    
    Args:
        membername: A String containing the member name.
        groupname: A String containing the group name.
        ownername: A String containing the owner name.
        status: A String containing the status of the membership.
        timestamp: A float value containing the exact timestamp of the membership creation.
    
    Returns:
        PTPModel.ReturnType.SUCCESSFUL is returned if the insertion was successful,
            PTPModel.MemberStatusChangedReturnType.ERROR if an error occurred,
            or PTPModel.MemberStatusChangedReturnType.GROUP_NOT_EXISTING if the group to update does not exist in the database.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.updateMemberGroup(membername, groupname, ownername, status, timestamp)
    finally:
        ptpDBLock.release()
        
def insertUserGroupAndGroup(membername, groupname, ownername):
    """Inserts the given user-group relation into the database, and, if not already present, it inserts the group (only group name and owner).
    
    Args:
        membername: A String containing the member name.
        groupname: A String containing the group name.
        ownername: A String containing the owner name.
    
    Returns:
        True is returned if the insertion was successful, False otherwise.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.insertUserGroupAndGroup(membername, groupname, ownername)
    finally:
        ptpDBLock.release()
        
def setIsActiveGroupForGroupnameAndOwnername(groupname, ownername, isActive):
    """Updates the column "isActive" for the entry in the group table with the given group name and owner name.
    
    Args:
        groupname: A String containing the group name.
        ownername: A String containing the owner name.
        isActive: Boolean value, True if the group is still active, False otherwise. 
    
    Returns:
        True is returned if the update was successful, False otherwise.
    """
    global ptpDBLock
    
    ptpDBLock.acquire()
    
    try:
        return PTPDBController.setIsActiveGroupForGroupnameAndOwnername(groupname, ownername, isActive)
    finally:
        ptpDBLock.release()
        
def insertChatMessage(username, partnername, chatMessage):
    """Inserts the given chat message and relates it to the given user name and partner name.
    
    Args:
        username: A String containing the user name.
        partnername: A String containing the user name of the chat partner.
        chatMessage: A String containing the chat message to be inserted.
    
    Returns:
        True is returned if the insertion was successful, False otherwise.
    """
    global ptpDBLock
    ptpDBLock.acquire()
    try:
        return PTPDBController.insertChatMessage(username, partnername, chatMessage)
    finally:
        ptpDBLock.release()
        
def updateChatMessageForSendernameAndTimestamp(sendername, timestamp, status):
    """Updates the status of the chat message, that corresponds to the given user name of the sender and the given timestamp.
    
    Args:
        sendername: A String containing the user name of the sender.
        timestamp: A float value containing the exact timestamp of the sending.
        status: A String representing the new status the corresponding chat message should have.
    
    Returns:
        True is returned if the update was successful, False otherwise.
    """
    global ptpDBLock
    ptpDBLock.acquire()
    try:
        return PTPDBController.updateChatMessageForSendernameAndTimestamp(sendername, timestamp, status)
    finally:
        ptpDBLock.release()

#####################################
##### MEDIA-database connections ####
#####################################

def initMediaDatabase(host, user, password, schema):
    """Initializes the media database.
    
    Sets host, username, password and schema to the corresponding arguments.
    The schema must be set to simulate multiple peers on one system (for testing).
    Furthermore, in the table NetSynchChanges it sets the value of the column "relevant" to false for all existing entries.
    
    Args:
        host: A String representing the host name of the database.
        username: A String representing the user name.
        password: A String representing the password of the user.
        schema: A String representing the schema name.
    """
    global mediaDBLock
    successful = False
    mediaDBLock.acquire()
    try:
        successful = MediaDBController.existsSchemaAndTables(host, user, password, schema)
        # init schema and tables and make initial import from player
        if successful == False:
            Logger.info("Media DB is void")
            successful = MediaDBController.initSchema()
        else:
            Logger.info("Media DB does not have to be initialized")
            MediaDBController.setNetSychnChangeRelevantToFalse()
    finally:
        mediaDBLock.release()
    return successful
        
def selectSynchInsertChanges():
    """Selects all insert entries of the synch_changes table, i.e. entries with the value INSERT at the column "event".
    
    Returns:
        A list of SynchChanges objects or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectSynchInsertChanges()
    finally:
        mediaDBLock.release()

def selectSynchUpdateChanges():
    """Selects all update entries of the synch_changes table, i.e. entries with the value UPDATE at the column "event".
    
    Returns:
        A list of SynchChanges objects or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectSynchUpdateChanges()
    finally:
        mediaDBLock.release()
        
def selectSynchChangesSize():
    """Retrieves the number of entries of the SynchChanges table.
    
    Returns:
        An integer number representing the number of entries of the SynchChanges table or 0 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectSynchChangesSize()
    finally:
        mediaDBLock.release()

def selectNetSynchMediaIdsProcessedSince(timestamp):
    """Selects all entries of the NetSynchChanges table that were processed after the given timestamp,
    i.e. where the value of the column "timestamp" is bigger than the given timestamp.
    
    Args:
        timestamp: A float value representing a certain timestamp.
        
    Returns:
        A list of NetSynchChanges objects is returned, or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectNetSynchMediaIdsProcessedSince(timestamp)
    finally:
        mediaDBLock.release()
        
def selectNetSynchMediasFromPlayerSince(lastProcessedId):
    """Selects all entries of the NetSynchChanges table that were inserted because of changes in the player and with an id higher than the given one,
    i.e. where the value of the column "source" is PLAYER and the value of the column "id" is bigger than the given id.
    
    Args:
        lastProcessedId: An integer number representing the id of a certain entry in the NetSynchChanges table.
        
    Returns:
        A list of NetSynchChanges objects is returned, or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectNetSynchMediasFromPlayerSince(lastProcessedId)
    finally:
        mediaDBLock.release()

def selectNetSynchMediasFromNetworkProcessedSince(lastProcessedId):
    """Selects all entries of the NetSynchChanges table that were inserted because of changes in the network and with an id higher than the given one,
    i.e. where the value of the column "source" is NETWORK and the value of the column "id" is bigger than the given id.
    
    Args:
        lastProcessedId: An integer number representing the id of a certain entry in the NetSynchChanges table.
        
    Returns:
        A list of NetSynchChanges objects is returned, or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectNetSynchMediasFromNetworkProcessedSince(lastProcessedId)
    finally:
        mediaDBLock.release()
    

def insertNetSynchChange(netSynchChange):
    """Inserts the given NetSynchChanges object into the NetSynchChanges table.
    
    Args:
        netSynchChange: The NetSynchChanges object to be inserted.
        
    Returns:
        True is returned if the insertion was successful, False otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertNetSynchChange(netSynchChange)
    finally:
        mediaDBLock.release()
        
def setLastNetSynchIdForUserAndGroup(username, groupname, ownername, id):
    """Updates or inserts in the LastNetSynch table the given id as "last_netsynch_id" for the given user name, group name and owner name.
    
    Args:
        username: A String representing the user name.
        groupname: A String representing the group name.
        ownername: A String representing the owner name.
        id: An integer representing the id to be inserted or updated.
        
    Returns:
        True is returned if the insertion or update was successful, False otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.setLastNetSynchIdForUserAndGroup(username, groupname, ownername, id)
    finally:
        mediaDBLock.release()
        
def selectLastUpdateProcessedTimestamp(groupname, ownername):
    """Selects the timestamp of the entry in the NetSynchChanges table that has the given group name and owner name.
    
    Args:
        groupname: A String representing the group name.
        ownername: A String representing the owner name.
        
    Returns:
        A float containing the stored timestamp is returned, or -1 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectLastUpdateProcessedTimestamp(groupname, ownername)
    finally:
        mediaDBLock.release()

def selectLastNetSynchId():
    """Selects the highest value of the column "id" of the table NetSynchChanges.
    
    Returns:
        An integer containing the highest id is returned, or -1 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectLastNetSynchId()
    finally:
        mediaDBLock.release()

def selectLastNetSynchIdForUserAndGroup(username, groupname, ownername):
    """Selects from the LastNetSynch table the value of the column "last_netsynch_id"
    that corresponds to the given user name, group name and owner name. 
    
    Args:
        username: A String representing the user name.
        groupname: A String representing the group name.
        ownername: A String representing the owner name.
    
    Returns:
        An integer containing the id is returned, or -1 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectLastNetSynchIdForUserAndGroup(username, groupname, ownername)
    finally:
        mediaDBLock.release()

def selectNearestNetSynchIdForTimestamp(timestamp):
    """Selects from the NetSynchChanges table the value of the column "id" where
    the value of the column "timestamp" is nearest to the given timestamp. 
    
    Args:
        timestamp: A float containing a certain timestamp.
    
    Returns:
        An integer containing the id is returned, or -1 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectNearestNetSynchIdForTimestamp(timestamp)
    finally:
        mediaDBLock.release()
        
def selectNetSynchIdForGroupInitial(groupname, ownername):
    """Selects from the NetSynchChanges table the value of the column "id"
    that corresponds to the given group name and owner name and where the value of the column "source" is INITIAl.
    
    Args:
        groupname: A String representing the group name.
        ownername: A String representing the owner name.
    
    Returns:
        An integer containing the id is returned, or -1 if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectNetSynchIdForGroupInitial(groupname, ownername)
    finally:
        mediaDBLock.release()
    
def deleteSynchChanges():
    """Deletes all entries of the NetSynchChanges table.
    
    Returns:
        True, if the deletion was successful, False otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.deleteSynchChanges()
    finally:
        mediaDBLock.release()
        
def selectArtistForName(artistName):
    """Selects from the media database the artist with the given name.
    
    All artists are selected and matched with the given name (not exact comparison).
    The first one, that is similar to the given name is returned.
    
    Args:
        artistName: A String representing the searched artist's name.
    
    Returns:
        An Artist object containing the searched artist or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectArtistForName(artistName)
    finally:
        mediaDBLock.release()
        
def selectCollectionForNameAndArtist(collectionName, artistName):
    """Selects from the media database the collection with the given name and from the given artist.
    
    All audios are selected. Artist and collection of each audio are matched with the given ones (not exact comparison).
    The first collection that is similar to the given values is returned.
    
    Args:
        collectionName: A Sring representing the searched collection.
        artistName: A String representing the name of the collection's artist.
    
    Returns:
        A Collection object containing the searched collection and artist or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectCollectionForNameAndArtist(collectionName, artistName)
    finally:
        mediaDBLock.release()
        
def selectGenreForName(genreName):
    """Selects from the media database the genre with the given name.
    
    All genres are selected and matched with the given name (not exact comparison).
    The first one, that is similar to the given name is returned.
    
    Args:
        genreName: A String representing the searched genre's name.
    
    Returns:
        A Genre object containing the searched genre or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectGenreForName(genreName)
    finally:
        mediaDBLock.release()
        
def selectAudiosForMediaIds(mediaIds, ratingForUser=None):
    """Selects from the media database the audios corresponding to the given ids.
    
    Args:
        mediaIds: A list of integers containing the searched ids.
        ratingForUser: A list of Strings containing user names for which the rating of the Audio object should be selected and added.
            If None, then all existing ratings for the Audio object are retrieved. Default None.
    
    Returns:
        A list of Audio objects containing the searched audios.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAudiosForMediaIds(mediaIds, ratingForUser)
    finally:
        mediaDBLock.release()
        
def selectAudiosForTitleArtistAndCollection(title, artistName, collectionName):
    """Selects from the media database the audios corresponding to the given title, artist name and collection name.
    
    All audios are selected and matched with the given values (not exact comparison).
    All audios, that are similar to the given values are returned.
    
    Args:
        title: A String containing the title of the searched audio.
        artistName: A String containing the artist name of the searched audio.
        collectionName: A String containing the collection name of the searched audio.
    
    Returns:
        A list of Audio objects containing the searched audios.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAudiosForTitleArtistAndCollection(title, artistName, collectionName)
    finally:
        mediaDBLock.release()

def selectAudiosForArtistAndCollection(artistName, collectionName):
    """Selects from the media database the audios corresponding to the given artist name and collection name.
    
    All audios are selected and matched with the given values (not exact comparison).
    All audios, that are similar to the given values are returned.
    
    Args:
        artistName: A String containing the artist name of the searched audio.
        collectionName: A String containing the collection name of the searched audio.
    
    Returns:
        A list of Audio objects containing the searched audios.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAudiosForArtistAndCollection(artistName, collectionName)
    finally:
        mediaDBLock.release()
        
def selectAudiosForArtist(artistName):
    """Selects from the media database the audios corresponding to the given artist name.
    
    All audios are selected and matched with the given value (not exact comparison).
    All audios, that are similar to the given value, are returned.
    
    Args:
        artistName: A String containing the artist name of the searched audio.
    
    Returns:
        A list of Audio objects containing the searched audios.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAudiosForArtist(artistName)
    finally:
        mediaDBLock.release()
        
def selectAudiosForGenre(genreName):
    """Selects from the media database the audios corresponding to the given genre name.
    
    All audios are selected and matched with the given value (not exact comparison).
    All audios, that are similar to the given value, are returned.
    
    Args:
        genreName: A String containing the genre name of the searched audio.
    
    Returns:
        A list of Audio objects containing the searched audios.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAudiosForGenre(genreName)
    finally:
        mediaDBLock.release()
        
def selectAllAudios():
    """Selects from the media database all audios.
    
    Returns:
        A list of all Audio objects.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectAllAudios()
    finally:
        mediaDBLock.release()

def selectRatingForMediaIdAndUsername(mediaId, username):
    """Selects from the media database the rating of the given user for the given media.
    
    Args:
        mediaid: An integer containing the id of the media, for which the rating should be retrieved.
        username: A String containing the user name, for which the rating should be retrieved.
    
    Returns:
        A list of Rating objects.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.selectRatingForMediaIdAndUsername(mediaId, username)
    finally:
        mediaDBLock.release()
        
def selectPredefinedGenres():
    """Selects all predefined genres.
    
    Returns:
        A list of Genre objects is returned or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return PTPDBController.selectPredefinedGenres()
    finally:
        mediaDBLock.release()
   
def selectPredefinedGenreByName(genreName):
    """Selects the predefined genre that corrsponds to the given genre name.
    
    Args:
        genreName: A String containing the genre name.
    
    Returns:
        A list of Genre objects is returned or None if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return PTPDBController.selectPredefinedGenreByName(genreName)
    finally:
        mediaDBLock.release() 

def insertArtist(artist):
    """Inserts the given artist into the media database.
    
    Args:
        artist: An Artist object containing the artist to insert.
    
    Returns:
        The id of the inserted artist is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertArtist(artist)
    finally:
        mediaDBLock.release()
        
def insertCollection(collection):
    """Inserts the given collection into the media database.
    
    Args:
        collection: A Collection object containing the collection to insert.
    
    Returns:
        The id of the inserted collection is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertCollection(collection)
    finally:
        mediaDBLock.release()
        
def insertGenre(genre):
    """Inserts the given genre into the media database.
    
    Args:
        genre: A Genre object containing the genre to insert.
    
    Returns:
        The id of the inserted genre is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertGenre(genre)
    finally:
        mediaDBLock.release()
        
def insertRating(rating, mediaId):
    """Inserts the given rating for the given media id into the media database.
    
    Args:
        rating: A Rating object containing the rating to insert.
        mediaId: An integer representing the id of the media for which the rating should be inserted.
    
    Returns:
        The id of the inserted rating is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertRating(rating, mediaId)
    finally:
        mediaDBLock.release()
        
def insertAudio(audio):
    """Inserts the given audio into the media database.
    
    Args:
        audio: An Audio object containing the audio to insert.
    
    Returns:
        The id of the inserted audio is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertAudio(audio)
    finally:
        mediaDBLock.release()
        
def insertMedia(media):
    """Inserts the given media into the media database.
    
    Args:
        media: A Media object containing the media to insert.
    
    Returns:
        The id of the inserted media is returned if the insertion was successful, -1 otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.insertMedia(media)
    finally:
        mediaDBLock.release()
        
def updateArtist(oldArtist, newArtist):
    """Updates the given oldArtist stored in the media database with the values of the given newArtist.
    
    Args:
        oldArtist: An Artist object containing the valued of the old artist stored in the media database.
        newArtist: An Artist object containing the new values of the artist.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateArtist(oldArtist, newArtist)
    finally:
        mediaDBLock.release()
        
def updateCollection(oldCollection, newCollection):
    """Updates the given oldCollection stored in the media database with the values of the given newCollection.
    
    Args:
        oldCollection: A Collection object containing the valued of the old collection stored in the media database.
        newCollection: A Collection object containing the new values of the collection.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateCollection(oldCollection, newCollection)
    finally:
        mediaDBLock.release()
        
#TODO deprecated
def updateGenre(oldGenre, newGenre):
    """Updates the given oldGenre stored in the media database with the values of the given newGenre.
    
    Args:
        oldGenre: A Genre object containing the valued of the old genre stored in the media database.
        newGenre: A Genre object containing the new values of the genre.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateGenre(oldGenre, newGenre)
    finally:
        mediaDBLock.release()
        
def updateRating(oldRating, newRating):
    """Updates the given oldRating stored in the media database with the values of the given newRating.
    
    Args:
        oldRating: A Rating object containing the valued of the old rating stored in the media database.
        newRating: A Rating object containing the new values of the rating.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateRating(oldRating, newRating)
    finally:
        mediaDBLock.release()
        
def updateMedia(oldMedia, newMedia):
    """Updates the given oldMedia stored in the media database with the values of the given newMedia.
    
    Args:
        oldMedia: A Media object containing the valued of the old media stored in the media database.
        newMedia: A Media object containing the new values of the media.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateMedia(oldMedia, newMedia)
    finally:
        mediaDBLock.release()
        
def updateAudio(oldAudio, newAudio):
    """Updates the given oldAudio stored in the media database with the values of the given newAudio.
    
    Args:
        oldAudio: An Audio object containing the valued of the old audio stored in the media database.
        newAudio: An Audio object containing the new values of the audio.
    
    Returns:
        It returns MediaModel.ReturnType.UPDATED if the update was successful,
            MediaModel.ReturnType.NOT_UPDATED is returned if the values stored in the database are the same as the new values,
            or MediaModel.ReturnType.ERROR if an error occurred.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.updateAudio(oldAudio, newAudio)
    finally:
        mediaDBLock.release()
        
def deleteMedia(mediaId):
    """Deletes from the media database the media with the given id.
    
    Args:
        mediaId: An integer representing the id of the media to delete.
    
    Returns:
        True if the deletion was successful, False otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.deleteMedia(mediaId)
    finally:
        mediaDBLock.release()
        
def deleteCollection(collectionId):
    """Deletes from the media database the collection with the given id.
    
    Args:
        collectionId: An integer representing the id of the collection to delete.
    
    Returns:
        True if the deletion was successful, False otherwise.
    """
    global mediaDBLock
    mediaDBLock.acquire()
    try:
        return MediaDBController.deleteCollection(collectionId)
    finally:
        mediaDBLock.release()


######################################
##### PLAYER-database connections ####
######################################

def initPlayerDatabase(playerDBSettings, mediaDBSchema):
    """Initializes the player database.
    
    Sets host, username, password and schema to the corresponding arguments.
    Initializes the triggers that log insert and update changes in the player database into a table of the media database.
    
    Args:
        playerDBSettings: A DatabaseSettings object containing host, username, password and schema for the player database.
        mediDBSchema: A String containing the schema name of the media database, needed for the triggers.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
#        player = playerDBSettings.getPlayer()
        host = playerDBSettings.getHost()
        user = playerDBSettings.getUser()
        password = playerDBSettings.getPassword()
        schema = playerDBSettings.getSchema()
        PlayerDBController.initDatabaseCredentials(host, user, password, schema)
        PlayerDBController.initTriggers(mediaDBSchema)
    finally:
        playerDBLock.release()

def selectTrackIdsForTitleArtistAndCollection(player, title, artistName, collectionName):
    """Selects from the given player database the ids for all tracks that correspond to the given title, artist name and collection name.
    
    Args:
        player: A String representing the player which database should be queried.
        title: A String containing the title of the track.
        artistName: A String containing the track's artist name.
        collectionName: A String containing the track's collection name.
    
    Returns:
        A list of integers representing the ids is returned, or None if an error occurred.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
        return PlayerDBController.selectTrackIdsForTitleArtistAndCollection(player, title, artistName, collectionName)
    finally:
        playerDBLock.release()
        
def selectFileUrlForTrackId(player, trackId):
    """Selects from the given player database the file url for the given track id.
    
    Args:
        player: A String representing the player which database should be queried.
        trackId: An integer containing the id of the track.
    
    Returns:
        A String containing the retrieved url is returned, or None if an error occurred.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
        return PlayerDBController.selectFileUrlForTrackId(player, trackId)
    finally:
        playerDBLock.release()

def selectPlayerAudiosFromPlayerDB(player=MediaModel.Player.AMAROK().getName(), trackId=None):
    """Selects from the given player database the audio with the given track id or all audios if trackId is None.
    
    Args:
        player: A String representing the player which database should be queried. Default AMAROK.
        trackId: An integer containing the id of the track. Default None.
    
    Returns:
        A list of Audio objects containing the retrieved audios of the player database, or None if an error occurred.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
        return PlayerDBController.selectAudio(player, trackId)
    finally:
        playerDBLock.release()
        
def selectPlayerTrackId(tablename, rowId, player=MediaModel.Player.AMAROK().getName()):
    """Selects from the given player database the id of the track that has an entry in the given row in the given table.
    
    Args:
        tablename: A String containing the name of a certain table of the player database.
        rowId: An integer value containing a certain row number.
        player: A String representing the player which database should be queried. Default AMAROK.
    
    Returns:
        An integer value containing the retrieved track id is returned, or None if an error occurred.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
        return PlayerDBController.selectTrackId(player, tablename, rowId)
    finally:
        playerDBLock.release()
        
def insertOrUpdateLyricsForUrl(player, lyrics, url):
    """Inserts or updates in the given player database the lyrics of the track that corresponds to the given url.
    
    Args:
        player: A String representing the player which database should be queried. Default AMAROK.
        lyrics: A String containing the lyrics to insert or update.
        url: A String containing the file url of the track which lyrics should be updated or inserted.
    
    Returns:
        True is returned if the update or insertion was successful, False otherwise.
    """
    global playerDBLock
    playerDBLock.acquire()
    try:
        return PlayerDBController.insertOrUpdateLyricsForUrl(player, lyrics, url)
    finally:
        playerDBLock.release()
